#include "msg_passing.h"
#include <sys/sem.h>

#define QKEY (key_t)0x58  /*initial identifying key for queue*/
#define QKEYC (key_t)0x62  /*initial identifying key for queue*/
#define SEMKEY	(key_t)0x25 /* shared sem key */

struct q_entry {
	long mtype;
	char mtext[MAXSIZE_QUEUE_INFO];
};

typedef union _semun
{
	int val;
	struct semid_ds * buf;
	ushort * array;
}semun;

static int isServer;
/*id for server queue*/
static int mqid;
/*id for client queue*/
static int mqid_client;

static int semid;


static int sendDataServer(void * data, int size);
static int sendDataClient(void * data, int size);
static int recvDataServer(void * data);
static int recvDataClient(void * data);
static void init_client(void);
static void init_server(void);
static void close_client(void);
static void close_server(void);
static void init_sem(void);


/*the pid generate uniqueness for the information send.
This prevents theoretically collision of multiple clients.
*/
static long client_pid;

/*Used to identify the current client the server is attending*/
static long client_msg_type;

/* send data */
int 
sendData(void * data, int size)
{
	
	
	if(isServer)
		return sendDataServer(data, size);
	else
		return sendDataClient(data, size);

}

/* receive data */
int
recvData(void * data)
{
	int size;

	if(isServer)
		size = recvDataServer(data);
	else
		size = recvDataClient(data);
		
	return size;
}

/* init configuration */
void 
init(int isThisTheServer)
{
 	isServer = isThisTheServer;
	
	if(isServer)
	{
		atexit(close_server);
		init_server();
	}
	else
	{
		atexit(close_client);
		init_client();
	}
}


/* 
**********************
** Auxiliar functions
**********************
*/

static int
sendDataServer(void * data, int size)
{
	struct q_entry entry;
	
	//get client message queue
	//mqid_client = msgget(client_msg_type, 0);

	//defensive programming
	if(size > MAXSIZE_QUEUE_INFO)
	{
		warn("Server: Size of sending information too long"
		" Truncation aplicated.");
		size = MAXSIZE_QUEUE_INFO;
	}

	//set reply id
	entry.mtype = 1;//client_msg_type;
	//copy information
	if(memcpy(entry.mtext, data, size)==NULL)
		warn("Server: Unknown error in the sending information");

	if(msgsnd(mqid_client, &entry, size, 0)==-1)
		warn("Server: Error while sending information");

	return size;
}

static int
sendDataClient(void * data, int size)
{
	struct q_entry entry;
	struct sembuf p={0,-1,0};
	
	if(size > MAXSIZE_QUEUE_INFO)
	{
		warn("Client: Size of sending information too long."
		" Truncation aplicated");
		size = MAXSIZE_QUEUE_INFO;
	}

	//set my id
	entry.mtype = 1;//client_pid;
	if(memcpy(entry.mtext, (char*)data, size)==NULL)
		fatal("Client: Unknown error in the sending information");

	semop(semid, &p, 1);
	
	if(msgsnd(mqid, &entry, size, 0) < 0)
		fatal("Client: Error while sending information");

	
	return size;
}

static int
recvDataServer(void * data)
{
	struct q_entry entry;
	int length;
	
	//recieve information
	if((length = msgrcv(mqid, &entry, MAXSIZE_QUEUE_INFO, 0, 0)) < 0)
		warn("Server: Error while receiving information");
	
	//get the client id so i know who to send the reply
	client_msg_type = entry.mtype;
	
	if((memcpy((char*)data, entry.mtext, length))==NULL)
		warn("Server: Error in the receiving information");

	return length;
}

static int
recvDataClient(void * data)
{
	struct q_entry entry;
	struct sembuf v ={0,1,0};
	int length;
	
	//recieve information
	if((length = msgrcv(mqid_client, &entry, MAXSIZE_QUEUE_INFO, 0, 0)) < 0)
		fatal("Client: Error while receiving information");
	

	if((memcpy((char*)data, entry.mtext, length))==NULL)
		fatal("Client: Error in the receiving information");

	/* enable a new request */
	semop(semid, &v, 1);

	return length;
}

/*Initialization of client */
static void
init_client(void)
{

	client_pid = (long)getpid();

	
	//get servers message queue
	mqid_client = msgget(QKEYC, 0);

	
	//get servers message queue
	mqid = msgget(QKEY, 0);

	if((semid=semget(SEMKEY, 1, 0))==-1)
		fatal("Client: Error while joing the semaphore set");
	

	//some type of error ocurred
	if(mqid < 0)
		fatal("Client: Unknown error while initializing "
			"the transport protocol");
}

static void
close_client(void)
{
	/*if((msgctl(mqid_client, IPC_RMID, NULL))== -1)
		fatal("Client: Fatal error while shutting down client");*/
}

static void
init_server(void)
{

	//create a new message queue
	mqid = msgget(QKEY, 0666 | IPC_CREAT | IPC_EXCL);

	//create a new message queue
	mqid_client = msgget(QKEYC, 0666 | IPC_CREAT);

	
	
	//some type of error ocurred
	if(mqid == -1)
	{	//The queue already exists
		if(errno == EEXIST)
			fatal("Server: Can not initilize multiple instances "
				"transport protocol. Server already up probably");
		else
			fatal("Server: Unknown error while initializing "
				"the transport protocol");
	}

	init_sem();
}

static void
close_server(void)
{	
	if((msgctl(mqid, IPC_RMID, NULL))== -1)
		fatal("Server: Fatal error while shutting down server");

	if((msgctl(mqid_client, IPC_RMID, NULL))== -1)
		fatal("Server: Fatal error while shutting down server");

	if(semctl(semid, 0,IPC_RMID)==-1)
		fatal("Server: Error while removing semaphore");
}

static void
init_sem(void)
{
	semun x;

	/* create three semaphore set */
	if((semid=semget(SEMKEY, 1, 0666 | IPC_CREAT))==-1)
		fatal("Server: Error while creating the semaphore set");

	/* set initial values */
	x.val=1;
	if(semctl(semid, 0, SETVAL, x)==-1)
		fatal("Server: Error while initialization semaphore");


}
